﻿//---------------------------------------------------------------------
// <copyright file="AddMissingNamesFixup.cs" company="Microsoft">
//      Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------

namespace Microsoft.Test.Taupo.EntityModel
{
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.Test.Taupo.Contracts;
    using Microsoft.Test.Taupo.Contracts.EntityModel;

    /// <summary>
    /// Replaces missing names (type, member, container and set names) with names generated by provided <see cref="IIdentifierGenerator"/>.
    /// </summary>
    public class AddMissingNamesFixup : IEntityModelFixupWithValidate
    {
        private IIdentifierGenerator identifierGenerator;

        /// <summary>
        /// Initializes a new instance of the AddMissingNamesFixup class object.
        /// </summary>
        /// <param name="identifierGenerator">Identifier generator to use.</param>
        public AddMissingNamesFixup(IIdentifierGenerator identifierGenerator)
        {
            this.identifierGenerator = identifierGenerator;
        }

        /// <summary>
        /// Replaces missing names (type, member, container and set names) with names generated by provided <see cref="IIdentifierGenerator"/>.
        /// </summary>
        /// <param name="model">Model to work on.</param>
        public void Fixup(EntityModelSchema model)
        {
            this.FixupEntityTypes(model.EntityTypes);
            this.FixupComplexTypes(model.ComplexTypes);
            this.FixupEntityContainers(model.EntityContainers);
            this.FixupAssociations(model.Associations);
            this.FixupFunctions(model.Functions);
            this.FixupEnumTypes(model.EnumTypes);
        }

        /// <summary>
        /// Checks whether the Validation Rule has been met
        /// </summary>
        /// <param name="model">Model being checked</param>
        /// <returns>
        /// Returns whether validation rule has been met
        /// </returns>
        public bool IsModelValid(EntityModelSchema model)
        {
            return EntityTypesValid(model)
                && ComplexTypesValid(model)
                && AssociationTypesValid(model)
                && FunctionsValid(model)
                && EntityContainersValid(model)
                && EnumTypesValid(model);
        }

        private static bool EntityContainersValid(EntityModelSchema model)
        {
            return model.EntityContainers.All(
                        t => t.Name != null && 
                             t.EntitySets.All(p => p.Name != null) && 
                             t.AssociationSets.All(p => p.Name != null) &&
                             t.FunctionImports.All(f => f.Name != null && f.Parameters.All(p => p.Name != null)));
        }

        private static bool FunctionsValid(EntityModelSchema model)
        {
            return model.Functions.All(t => t.Name != null && t.Parameters.All(p => p.Name != null));
        }

        private static bool AssociationTypesValid(EntityModelSchema model)
        {
            return model.Associations.All(t => t.Name != null && t.Ends.All(p => p.RoleName != null));
        }

        private static bool ComplexTypesValid(EntityModelSchema model)
        {
            return model.ComplexTypes.All(t => t.Name != null && t.Properties.All(p => p.Name != null));
        }

        private static bool EntityTypesValid(EntityModelSchema model)
        {
            return model.EntityTypes.All(t => t.Name != null && t.Properties.All(p => p.Name != null) && t.NavigationProperties.All(p => p.Name != null));
        }

        private static bool EnumTypesValid(EntityModelSchema model)
        {
            return model.EnumTypes.All(t => t.Name != null && t.Members.All(m => m.Name != null));
        }

        private void FixupEntityTypes(IEnumerable<EntityType> entityTypes)
        {
            foreach (EntityType type in entityTypes)
            {
                if (type.Name == null)
                {
                    type.Name = this.GenerateUniqueNameInCategory("EntityType");
                }

                this.FixupPropertyNames(type.Properties);
                this.FixupNavigationPropertyNames(type.NavigationProperties);
            }
        }

        private void FixupPropertyNames(IEnumerable<MemberProperty> properties)
        {
            foreach (MemberProperty prop in properties)
            {
                if (prop.Name == null)
                {
                    prop.Name = this.GenerateUniqueNameInCategory("Property");
                }
            }
        }

        private void FixupNavigationPropertyNames(IEnumerable<NavigationProperty> properties)
        {
            foreach (NavigationProperty prop in properties)
            {
                if (prop.Name == null)
                {
                    prop.Name = this.GenerateUniqueNameInCategory("NavigationProperty");
                }
            }
        }

        private void FixupEntityContainers(IEnumerable<EntityContainer> entityContainers)
        {
            foreach (EntityContainer container in entityContainers)
            {
                if (container.Name == null)
                {
                    container.Name = this.GenerateUniqueNameInCategory("EntityContainer");
                }

                this.FixupSetNames(container.EntitySets);
                this.FixupSetNames(container.AssociationSets);
                this.FixupFunctionImports(container.FunctionImports);
            }
        }

        private void FixupSetNames(IEnumerable<EntitySet> sets)
        {
            foreach (EntitySet set in sets)
            {
                if (set.Name == null)
                {
                    set.Name = this.GenerateUniqueNameInCategory("EntitySet");
                }
            }
        }

        private void FixupSetNames(IEnumerable<AssociationSet> sets)
        {
            foreach (AssociationSet set in sets)
            {
                if (set.Name == null)
                {
                    set.Name = this.GenerateUniqueNameInCategory("AssociationSet");
                }
            }
        }

        private void FixupComplexTypes(IEnumerable<ComplexType> complexTypes)
        {
            foreach (ComplexType type in complexTypes)
            {
                if (type.Name == null)
                {
                    type.Name = this.GenerateUniqueNameInCategory("ComplexType");
                }
                
                this.FixupPropertyNames(type.Properties);
            }
        }

        private void FixupAssociations(IEnumerable<AssociationType> associationTypes)
        {
            foreach (AssociationType association in associationTypes)
            {
                if (association.Name == null)
                {
                    association.Name = this.GenerateUniqueNameInCategory("Association");
                }

                this.FixupAssociationEnds(association.Ends);
            }
        }

        private void FixupAssociationEnds(IEnumerable<AssociationEnd> ends)
        {
            foreach (AssociationEnd end in ends)
            {
                if (end.RoleName == null)
                {
                    end.RoleName = this.GenerateUniqueNameInCategory("Role");
                }
            }
        }

        private void FixupFunctions(IEnumerable<Function> functions)
        {
            foreach (Function f in functions)
            {
                if (f.Name == null)
                {
                    f.Name = this.GenerateUniqueNameInCategory("Function");
                }

                this.FixupFunctionParameters(f.Parameters);
            }
        }

        private void FixupFunctionParameters(IEnumerable<FunctionParameter> parameters)
        {
            foreach (FunctionParameter p in parameters)
            {
                if (p.Name == null)
                {
                    p.Name = this.GenerateUniqueNameInCategory("Parameter");
                }
            }
        }

        private void FixupFunctionImports(IEnumerable<FunctionImport> functionImports)
        {
            foreach (FunctionImport f in functionImports)
            {
                if (f.Name == null)
                {
                    f.Name = this.GenerateUniqueNameInCategory("FunctionImport");
                }

                this.FixupFunctionParameters(f.Parameters);
            }
        }

        private void FixupEnumTypes(IEnumerable<EnumType> enumTypes)
        {
            foreach (EnumType e in enumTypes)
            {
                if (e.Name == null)
                {
                    e.Name = this.GenerateUniqueNameInCategory("EnumType");
                }

                this.FixupEnumMembers(e.Members);
            }
        }

        private void FixupEnumMembers(IEnumerable<EnumMember> enumOptions)
        {
            foreach (EnumMember m in enumOptions)
            {
                if (m.Name == null)
                {
                    m.Name = this.GenerateUniqueNameInCategory("EnumMember");
                }
            }
        }

        private string GenerateUniqueNameInCategory(string categoryName)
        {
            return this.identifierGenerator.GenerateIdentifier(categoryName);
        }
    }
}
